/*!
 * @file model.cpp
 * @brief Ciało model.hh
 */

#include "engine/basic/model.hh"
using namespace std;

/*--------------------------------------------------------------------------*/

model::model(string path) {
	this->path = path;

	this->loadObj();
	this->loadMass();

	this->tx = new texture("pawirr/models/krulspeld1.jpg");
}

/*--------------------------------------------------------------------------*/

void model::loadMass() {
	char tmpt[1024];
	ifstream file((path + ".mass").c_str());

	modelMass *active_data = NULL;
	stringstream ss;

	while (file.getline(tmpt, 1024)) {
		active_data = new modelMass;

		ss << tmpt;
		ss >> active_data->position.x >> active_data->position.y
				>> active_data->position.z >> active_data->weigth;

		this->mass.push_back(active_data);

		ss.str("");
		ss.clear();
	}

	file.close();
}

/*--------------------------------------------------------------------------*/

const list<modelMass*> *model::getMassList() const {
	return &this->mass;
}

/*--------------------------------------------------------------------------*/

void model::loadObj() {

	char tmpt[1024];
	ifstream file((path + ".obj").c_str());

	stringstream ss;

	string test;
	string group;
	string subgroup;

	modelData *active_data = NULL;

	float x, y, z;

	while (file.getline(tmpt, 1024)) {

		ss.clear();
		ss.str("");

		ss << tmpt;
		ss >> test;

		if (test[0] == '#') {
			//cout << "ignore: " << tmpt << endl;
			continue;
		}

		/*
		 * Właściwe sprawdzanie warunków
		 */
		if (test == "g") {
			ss >> group >> subgroup;

			if (active_data != NULL)
				this->data.push_back(active_data);

			active_data = new modelData(group, subgroup);
			//cout << "nowa grupa: " << group << " sub: " << subgroup << endl;

		} else if (test == "v") { //wierzchołki
			ss >> x >> y >> z;

			if (!active_data) {
				active_data = new modelData(group, subgroup);
			}

			active_data->lVert.push_back(point3f(x, y, z));

			if (x > active_data->sizePlus.x)
				active_data->sizePlus.x = x;
			if (y > active_data->sizePlus.y)
				active_data->sizePlus.y = y;
			if (z > active_data->sizePlus.z)
				active_data->sizePlus.z = z;

			if (x < active_data->sizeMinus.x)
				active_data->sizeMinus.x = x;
			if (y < active_data->sizeMinus.y)
				active_data->sizeMinus.y = y;
			if (z < active_data->sizeMinus.z)
				active_data->sizeMinus.z = z;

		} else if (test == "vt") { //texcoord
			ss >> x >> y;

			//cout << "vt " << x << " " << y << endl;

			if (!active_data) {
				active_data = new modelData(group, subgroup);
			}

			active_data->lTexCoord.push_back(point3f(x, y, 0));

		} else if (test == "vn") { //normale
			ss >> x >> y >> z;

			if (!active_data) {
				active_data = new modelData(group, subgroup);
			}

			active_data->lNorm.push_back(point3f(x, y, z));
		} else if (test == "f") { //polygony

			if (!active_data) {
				active_data = new modelData(group, subgroup);
			}

			char tester;
			int tmpmf[9];
			ss >> tmpmf[0] >> tester >> tmpmf[1] >> tester >> tmpmf[2]
					>> tmpmf[3] >> tester >> tmpmf[4] >> tester >> tmpmf[5]
					>> tmpmf[6] >> tester >> tmpmf[7] >> tester >> tmpmf[8];
			/*
			 * Pomniejszanie wartości o 1 bo w *.obj liczone jest od 1, a tutaj od 0
			 */

			modelFace mf;
			mf.vert1 = tmpmf[0] - 1;
			mf.text1 = tmpmf[1] - 1;
			mf.norm1 = tmpmf[2] - 1;
			mf.vert2 = tmpmf[3] - 1;
			mf.text2 = tmpmf[4] - 1;
			mf.norm2 = tmpmf[5] - 1;
			mf.vert3 = tmpmf[6] - 1;
			mf.text3 = tmpmf[7] - 1;
			mf.norm3 = tmpmf[8] - 1;
			active_data->lFace.push_back(mf);

		} else if (test == "usemtl") { //materiał
			ss >> test;
			active_data->material = test;
		} else {
			continue;
		}

	}

	file.close();

	if (active_data)
		this->data.push_back(active_data);

	for (list<modelData*>::iterator i = this->data.begin(); i
			!= this->data.end(); ++i) {
		(*i)->fillVBO();
	}
}

/*--------------------------------------------------------------------------*/

point3f model::getSize() const {
	//cout << this->data.front()->sizePlus << this->data.front()->sizeMinus;

	if (this->data.size()) {
		return this->data.front()->sizePlus - this->data.front()->sizeMinus;
	} else {
		return point3f();
	}

}

/*--------------------------------------------------------------------------*/

void model::draw(bool drawBox) const {
	this->tx->bind();

	glCullFace(GL_FRONT);

	for (list<modelData*>::const_iterator i = this->data.begin(); i
			!= this->data.end(); ++i) {

		(*i)->shaderProgram->bind();
		glBindVertexArray((*i)->vao);

		GLuint vl = glGetUniformLocation((*i)->shaderProgram->getProgram(),
				"modelTexture");
		if (vl != -1) {
			glUniform1i(vl, 0);
		}

		glDrawArrays(GL_TRIANGLES, 0, (*i)->totalVertCount);

		(*i)->shaderProgram->unbind();

		if (drawBox) {
			glBegin(GL_LINES);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizeMinus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizeMinus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizePlus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glVertex3f((*i)->sizeMinus.x, (*i)->sizePlus.y, (*i)->sizePlus.z);
			glEnd();
		}

	}

	glCullFace(GL_BACK);
	glBindVertexArray(0);
}

/*--------------------------------------------------------------------------*/

modelData::modelData(std::string g, std::string sg) :
	group(g), subgroup(sg) {
}

/*--------------------------------------------------------------------------*/

void modelData::fillVBO() {

	this->totalVertCount = this->lFace.size() * 3;
	int faceSize = this->lFace.size();
	int vertSize = this->lVert.size();
	int textSize = this->lTexCoord.size();
	int normSize = this->lNorm.size();

	/*cout << "FillVBO: " << this->group << "/" << this->subgroup << endl;
	 cout << "lFace: " << faceSize << endl;
	 cout << "lVert: " << vertSize << endl;
	 cout << "lText: " << textSize << endl;
	 cout << "lNorm: " << normSize << endl;*/

	glGenVertexArrays(1, &this->vao);
	glGenBuffers(3, this->vbo);
	glBindVertexArray(this->vao);

	/*
	 * To zostanie wrzucone do VBO
	 */
	point3f *vertVBO = new point3f[this->totalVertCount];
	point3f *textVBO = new point3f[this->totalVertCount];
	point3f *normVBO = new point3f[this->totalVertCount];

	/*
	 * Tutaj zostanie przepisana cała lista do tablicy
	 */
	point3f *vertArray = new point3f[vertSize];
	point3f *textArray = new point3f[textSize];
	point3f *normArray = new point3f[normSize];

	int vertPos = 0;
	for (list<point3f>::iterator vert = this->lVert.begin(); vert
			!= this->lVert.end(); ++vert) {
		vertArray[vertPos++] = *vert;
	}
	int textPos = 0;
	for (list<point3f>::iterator text = this->lTexCoord.begin(); text
			!= this->lTexCoord.end(); ++text) {
		textArray[textPos++] = *text;
	}
	int normPos = 0;
	for (list<point3f>::iterator norm = this->lNorm.begin(); norm
			!= this->lNorm.end(); ++norm) {
		normArray[normPos++] = *norm;
	}

	/*
	 * Przelot przez wszystkie faces
	 */
	int mfPos = 0;
	for (list<modelFace>::iterator i = this->lFace.begin(); i
			!= this->lFace.end(); ++i) {

		if ((*i).vert1 < vertSize)
			vertVBO[mfPos * 3] = vertArray[(*i).vert1];

		if ((*i).text1 < textSize)
			textVBO[mfPos * 3] = textArray[(*i).text1];

		if ((*i).norm1 < normSize)
			normVBO[mfPos * 3] = normArray[(*i).norm1];

		if ((*i).vert2 < vertSize)
			vertVBO[mfPos * 3 + 1] = vertArray[(*i).vert2];

		if ((*i).text2 < textSize)
			textVBO[mfPos * 3 + 1] = textArray[(*i).text2];

		if ((*i).norm2 < normSize)
			normVBO[mfPos * 3 + 1] = normArray[(*i).norm2];

		if ((*i).vert3 < vertSize)
			vertVBO[mfPos * 3 + 2] = vertArray[(*i).vert3];

		if ((*i).text3 < textSize)
			textVBO[mfPos * 3 + 2] = textArray[(*i).text3];

		if ((*i).norm3 < normSize)
			normVBO[mfPos * 3 + 2] = normArray[(*i).norm3];

		++mfPos;
	}

	/*
	 * Tablicolisty są już zbędne. teraz akcja toczy się w tablicach VBO
	 */
	delete[] vertArray;
	delete[] textArray;
	delete[] normArray;

	/*for(int i=0; i< this->totalVertCount; i++){
	 cout << textVBO[i];
	 }*/
	/*
	 * Wierzchołki
	 */
	glBindBufferARB(GL_ARRAY_BUFFER, this->vbo[0]);
	glBufferDataARB(GL_ARRAY_BUFFER, this->totalVertCount * sizeof(point3f),
			vertVBO, GL_STATIC_DRAW_ARB);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

	/*
	 * Tekstury
	 */
	glBindBufferARB(GL_ARRAY_BUFFER, this->vbo[1]);
	glBufferDataARB(GL_ARRAY_BUFFER, this->totalVertCount * sizeof(point3f),
			textVBO, GL_STATIC_DRAW_ARB);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

	/*
	 * Normale
	 */
	glBindBufferARB(GL_ARRAY_BUFFER, this->vbo[2]);
	glBufferDataARB(GL_ARRAY_BUFFER, this->totalVertCount * sizeof(point3f),
			normVBO, GL_STATIC_DRAW_ARB);
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

	/*
	 * Tablice dla VBO skopiowane do VRAMU. można wywalić.
	 */
	delete[] vertVBO;
	delete[] textVBO;
	delete[] normVBO;

	this->shaderProgram = new shader("modelShader");
	this->shaderProgram->addShader("pawirr/shaders/model.v", ST_V);
	this->shaderProgram->addShader("pawirr/shaders/model.f", ST_F);

	glBindAttribLocation(this->shaderProgram->getProgram(), 0, "attPos");
	glBindAttribLocation(this->shaderProgram->getProgram(), 1, "attTex");
	glBindAttribLocation(this->shaderProgram->getProgram(), 2, "attNorm");

	pawirr::graphic->_env->addEnvUniforms(this->shaderProgram);

	this->shaderProgram->linkProgram();

	glBindVertexArray(0);
}

/*--------------------------------------------------------------------------*/
